/*
 * Copyright (c) 2019. André Mion
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.andremion.floatingnavigationview;

import ohos.agp.animation.AnimatorProperty;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.DependentLayout;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;
import ohos.utils.geo.Point;

/**
 * CircularRevealNavigationView侧边菜单结合浮动按钮的自定义View
 *
 * @author hw
 * @since 2021/5/24
 */
public class CircularRevealNavigationView extends DependentLayout implements Component.DrawTask,
        ComponentContainer.ArrangeListener, Component.ClickedListener, Component.TouchEventListener,
        Component.BindStateChangedListener {
    private Path mClipPath;

    private Path mOpClipPath;

    // 画笔
    private Paint mPaint;

    // 画布图层大小
    private RectFloat mLayer;

    // 菜单打开/关闭状态
    private boolean isOpenMenu;

    private AnimatorProperty menuAnimatorProperty;

    private AnimatorValue mStartingAnimator;

    private float mAnimatorValue;

    private Component menuLayout;

    private FloatingNavigationView mFabView;

    /**
     * Sets menu color.
     *
     * @param menuColor 菜单颜色
     */
    public void setMenuColor(int menuColor) {
        this.menuColor = menuColor;
    }

    private int menuColor;

    /**
     * Instantiates a new Circular reveal navigation view.
     *
     * @param context the context
     */
    public CircularRevealNavigationView(Context context) {
        super(context);
    }

    /**
     * Instantiates a new Circular reveal navigation view.
     *
     * @param context the context
     * @param attrs   the attrs
     */
    public CircularRevealNavigationView(Context context, AttrSet attrs) {
        super(context, attrs);
        setMenuColor(0xFFFF4081);
        initAttr();
        setArrangeListener(this);
        addDrawTask(this, BETWEEN_BACKGROUND_AND_CONTENT);
        setTouchEventListener(this);
        setBindStateChangedListener(this);
    }

    private void setupFabView() {
        ShapeElement element;
        if (mFabView.getBackgroundElement() instanceof ShapeElement) {
            element = (ShapeElement) mFabView.getBackgroundElement();
            int rValue = (menuColor >> 16) & 0xff;
            int gValue = (menuColor >> 8) & 0xff;
            int bValue = menuColor & 0xff;
            element.setRgbColor(new RgbColor(rValue, gValue, bValue));
            mFabView.setBackground(element);
        }
        mFabView.setClickedListener(this);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
        final float xPoint = point.getX();
        final float yPoint = point.getY();

        if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN
                && ((xPoint < 0) || (xPoint < menuLayout.getLeft())
                || (yPoint < 0) || (yPoint >= menuLayout.getHeight())) && isOpenMenu) {
            // 触碰菜单外界时关闭菜单
            startAnimal();
            return true;
        }
        return false;
    }

    @Override
    public void onClick(Component component) {
        startAnimal();
    }

    /**
     * 初始化画笔画布
     */
    private void initAttr() {
        mLayer = new RectFloat();
        mClipPath = new Path();
        mOpClipPath = new Path();
        mPaint = new Paint();
        mPaint.setColor(Color.WHITE);
        mPaint.setAntiAlias(true);
        initAnimator();
    }

    /**
     * Instantiates a new Circular reveal navigation view.
     *
     * @param context   the context
     * @param attrs     the attrs
     * @param styleName the style name
     */
    public CircularRevealNavigationView(Context context, AttrSet attrs, String styleName) {
        super(context, attrs, styleName);
    }

    /**
     * 初始化动画类
     */
    private void initAnimator() {
        // 拿到动画的执行的百分比mAnimatorValue
        AnimatorValue.ValueUpdateListener mUpdateListener = (animatorValue, value) -> {
            // 拿到动画的执行的百分比mAnimatorValue
            mAnimatorValue = isOpenMenu ? value : 1 - value;
            CircularRevealNavigationView.this.invalidate();
        };
        mStartingAnimator = new AnimatorValue();
        int defaultDuration = 400;
        mStartingAnimator.setDuration(defaultDuration);
        mStartingAnimator.setLoopedCount(0);
        mStartingAnimator.setValueUpdateListener(mUpdateListener);
    }

    /**
     * 计算揭露动画需要裁剪的圆形尺寸
     *
     * @param view 菜单view
     */
    public void refreshRegion(Component view) {
        RectFloat areas = new RectFloat();
        areas.left = (float) view.getLeft() + view.getPaddingLeft();
        areas.top = (float) view.getTop() + view.getPaddingTop();
        areas.right = (float) view.getRight() - view.getPaddingRight();
        areas.bottom = (float) view.getBottom() - view.getPaddingBottom();
        mClipPath.reset();

        int mDownX = mFabView.getLeft() + mFabView.getWidth() / 2;

        int mDownY = mFabView.getBottom() - mFabView.getHeight() / 2;

        float mMaxRadius = getMaxRadius(mDownX, mDownY, areas, getLeft()); /* 计算最大半径 */

        /* 计算最大半径 */
        Point center = new Point(mDownX, mDownY);
        float radius = mMaxRadius * mAnimatorValue;
        mClipPath.addCircle(center.getPointX(), center.getPointY(), radius, Path.Direction.CLOCK_WISE);
    }

    /**
     * getMaxRadius
     *
     * @param mDownX mDownX
     * @param mDownY mDownY
     * @param areas  areas
     * @param left   areas
     * @return float MaxRadius
     */
    public float getMaxRadius(int mDownX, int mDownY, RectFloat areas, int left) {
        /* 计算最大半径 */
        int radiusLeftTop = (int) Math.sqrt((double) mDownX * mDownX + (double) mDownY * mDownY);

        int radiusRightTop = (int) Math.sqrt(((double) areas.right - ((double) mDownX + left))
                * ((double) areas.right - ((double) mDownX + left))
                + (double) mDownY * mDownY);

        int radiusLeftBottom = (int) Math.sqrt((double) mDownX * mDownX +
                ((double) areas.getHeight() - mDownY) * ((double) areas.getHeight() - mDownY));

        int radiusRightBottom = (int) Math.sqrt(((double) areas.right - ((double) mDownX + left))
                * ((double) areas.right - ((double) mDownX + left))
                + ((double) areas.getHeight() - mDownY) * ((double) areas.getHeight() - mDownY));

        return Utils.getMax(new int[]{radiusLeftTop, radiusRightTop, radiusLeftBottom, radiusRightBottom});
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        canvas.save();
        onClipDraw(canvas);
    }

    /**
     * 根据animation的进度裁剪圆形path
     *
     * @param canvas canvas对象
     */
    private void onClipDraw(Canvas canvas) {
        mPaint.setColor(new Color(menuColor));
        mPaint.setStyle(Paint.Style.FILL_STYLE);
        mPaint.setAntiAlias(true);
        mOpClipPath.reset();
        mOpClipPath.addRect(mLayer.left, mLayer.top, mLayer.right, mLayer.bottom, Path.Direction.CLOCK_WISE);
        canvas.clipPath(mClipPath, Canvas.ClipOp.INTERSECT);
        canvas.drawPath(mOpClipPath, mPaint);
        canvas.restore();
    }

    @Override
    public void invalidate() {
        refreshRegion(menuLayout);
        super.invalidate();
    }

    /**
     * 开启动画
     */
    public void startAnimal() {
        mFabView.changeState(isOpenMenu);
        if (menuAnimatorProperty != null) {
            menuAnimatorProperty
                    .alpha(isOpenMenu ? 0 : 1)
                    .setDuration(100)
                    .start();
        }
        menuLayout.setVisibility(isOpenMenu ? INVISIBLE : VISIBLE);
        isOpenMenu = !isOpenMenu;
        mStartingAnimator.cancel();
        mLayer.fuse(menuLayout.getLeft(), menuLayout.getTop(),
                menuLayout.getRight(), menuLayout.getBottom());
        refreshRegion(menuLayout);
        mStartingAnimator.start();
    }

    @Override
    public void addDrawTask(DrawTask task) {
        super.addDrawTask(task);
        task.onDraw(this, mCanvasForTaskUnderContent);
    }

    /*
        获取子控件
     */
    @Override
    public boolean onArrange(int i0, int i1, int i2, int i3) {
        if (getChildCount() > 0 && menuLayout == null) {
            if (getComponentAt(0) instanceof FloatingNavigationView) {
                mFabView = (FloatingNavigationView) getComponentAt(0);
                mFabView.setAnimateDuration(200);
                setupFabView();
            }
            menuLayout = getComponentAt(1);
            menuAnimatorProperty = menuLayout.createAnimatorProperty();
        }
        return false;
    }

    @Override
    public void onComponentBoundToWindow(Component component) {
    }

    @Override
    public void onComponentUnboundFromWindow(Component component) {
        if (mStartingAnimator != null) {
            mStartingAnimator.cancel();
            menuAnimatorProperty.cancel();
            mStartingAnimator.release();
            menuAnimatorProperty.release();
        }
    }

    /**
     * Is open menu boolean.
     *
     * @return the boolean
     */
/*
        当前菜单状态
     */
    public boolean isOpenMenu() {
        return isOpenMenu;
    }
}